#!/usr/bin/env python3
import re
import numpy as np
import random

"""
函数说明：将接收到的字符串解析为字符串列表
parameter:
	bigString - 文本数据
returns:
	[tok.lower] - 切割好的文本数据list
"""
def textParse(bigString):
	listOfTokens = re.split(r'\W*',bigString)	#将文本中的所有单词都切割开
	return [tok.lower() for tok in listOfTokens if len(tok) > 2]	#将所有的除了I这些字母外的其他单词都转为小写并且返回

"""
函数说明：读取所有数据并保存返回
parameter：
	无
returns:
	docList - 存放的所有垃圾和正常邮件的所有数据
	classList - 标记垃圾和正常邮件
"""
def getData():
	docList = []
	classList = []
	for i in range(1,26):
		wordList = textParse(open('email/spam/%d.txt' %i,'r').read())	#读取垃圾邮件的数据
		docList.append(wordList)
		classList.append(1)
		wordList = textParse(open('email/ham/%d.txt' %i,'r').read())	#读取垃圾邮件的数据
		docList.append(wordList)
		classList.append(0)
	return docList,classList

"""
函数说明：将切分好的数据整理成不重合的词汇表
parameter:
	dataSet	- 切个好的文本数据List
returns:
	vocabList - 返回的不重复的词汇表
"""
def createVocabList(dataSet):
	vocabList = set([])	#创建一个空的集合
	for each in dataSet:
		vocabList = vocabList | set(each)	#取并集
	return list(vocabList)	#返回list()后的列表数据

"""
函数说明：向量化dataSet数据集
parameter:
	vocabListData - 数据集中的某一行数据
	vocabList - 词汇表
returns:
	vecDataSet - 经过词汇表向量化后的数据集
"""
def dataSet2Vec(vocabListData,vocabList):
	vecDataSet = [0] * len(vocabList)
	for eachData in vocabListData:
		if eachData in vocabList:
			vecDataSet[vocabList.index(eachData)] += 1
	return vecDataSet

"""
函数说明：将docList里面的所有数据向量化
parameter：
	docList - 数据集
	vocabList - 词汇表
return:
	docList2Vec - 向量化的数据集
"""
def docList2Vec(docList,vocabList):
	docList2Vec = []
	for eachList in docList:
		 docList2Vec.append(dataSet2Vec(eachList,vocabList))
	return docList2Vec

"""
函数说明：将数据分为训练数据和测试数据
parameter:
	docList2Vec - 向量化后的数据矩阵
	classList - 数据的种类（是否是垃圾邮件（0，1））
returns:
	trainMatrix - 训练数据
	docList2Vec - 测试数据
"""
def choiceTrainAndTest(docList2Vec,classList):
	dataLength = len(classList)
	testSet = [] #存储测试集数据的分类
	testMatrix = []	#存储测试数据
	for i in range(int(dataLength / 5)):	#取五分之一的数据作为测试集
		randomIndex = int(random.uniform(0,len(classList)))	#随机从（0，len()）之间选取数据
		testMatrix.append(docList2Vec[randomIndex])	#将其添加到测试集中
		testSet.append(classList[randomIndex]) #添加测试集的分类
		del(docList2Vec[randomIndex])	#删除已经添加到测试集中的数据
		del(classList[randomIndex])	#删除已经添加到测试集中的数据的分类数据
	return testMatrix,docList2Vec,testSet,classList


"""
函数说明：朴素贝叶斯分类器的训练函数
parameter:
	trainMatrix	- 训练文档数据
	trainSet - 训练类别标签
returns:
	p0Vec - 非侮辱类的条件概率
	p1Vec - 侮辱类的条件概率
	pAbusive - 文档属于侮辱类的概率
"""
def trainNB(trainMatrix,trainSet):
	lenTrainSet = len(trainSet)	#得到训练集的长度
	numWords = len(trainMatrix[0])	#得到训练集的词条数
	pAbusive = sum(trainSet) / float(lenTrainSet)	#计算侮辱类文档的概率
	p0Num = np.ones(numWords)	#创建numpy.ones数组,词条出现次数初始化为1，为拉普拉斯平滑创造条件
	p1Num = np.ones(numWords)
	p0Denom = 2.0	#分母初始化为2，拉普拉斯平滑
	p1Denom = 2.0
	for i in range(lenTrainSet):
		if trainSet[i] == 1:	#统计属于侮辱类的条件概率所需的数据
			p1Num += trainMatrix[i]
			p1Denom += sum(trainMatrix[i])
		else :
			p0Num += trainMatrix[i]	#统计属于非侮辱类的条件概率所需的数据
			p0Denom += sum(trainMatrix[i])
	p1Vect = np.log(p1Num / p1Denom)	#取对数,防止下溢出
	p0Vect = np.log(p0Num / p0Denom)	#取对数
	return p0Vect,p1Vect,pAbusive

"""
函数说明：朴素贝叶斯分类器分类函数
parameter:
	vec2Classify - 待分类的词条数组
	p0Vect - 非侮辱类的条件概率数组
	p1Vect - 侮辱类的条件概率数组
	pAbusive - 文档属于侮辱类的概率
return:
	0 - 属于非侮辱类
	1 - 属于非侮辱类
"""
def classfyNB(vec2Classify,p0Vect,p1Vect,pAbusive):
	p1 = sum(vec2Classify * p1Vect) + np.log(pAbusive) #对应元素相乘。logA * B = logA + logB，所以这里加上log(pAbusive)
	p0 = sum(vec2Classify * p0Vect) + np.log(1.0 - pAbusive)
	if p1 > p0:
		return 1
	else :
		return 0

"""
函数说明：对测试集进行测验
parameter:
	testMatrix - 训练集合
	testSet - 训练集合类别
	p0Vect - 非侮辱类的条件概率数组
	p1Vect - 侮辱类的条件概率数组
	pAbusive - 文档属于侮辱类的概率
return:
	errorCount - 错误率
	errorSet - 错误的集合的位置(这个东西有点难办，要得到最开始的数据集的位置时候就应该在处理dataSet的时候就标记好位置)
"""
def testNB(testMatrix,testSet,p0Vect,p1Vect,pAbusive):
	errorCount = 0
	errorSet = []
	# print(testMatrix)
	numOfTestSet = len(testSet)
	for i in range(numOfTestSet):
		if classfyNB(testMatrix[i],p0Vect,p1Vect,pAbusive) != testSet[i]:
			errorCount += 1
			errorSet.append(i)
			print("分类错误的测试集：",testMatrix[i])
	return float(errorCount) / len(testSet),errorSet

if __name__ == '__main__':
	docList,classList = getData()	#获取到数据
	# print(classList)
	vocabList = createVocabList(docList)	#得到词汇表
	docList2Vec =  docList2Vec(docList,vocabList)
	# print(len(docList2Vec),len(classList))
	testMatrix,trainMatrix,testSet,trainSet = choiceTrainAndTest(docList2Vec,classList)	#取得测试集和训练集的数据和分类情况
	p0Vect,p1Vect,pAbusive = trainNB(trainMatrix,trainSet)	#得到训练集的参数
	errorCount,errorSet = testNB(testMatrix,testSet,p0Vect,p1Vect,pAbusive)	#带入测试集计算错误率
	print("错误率:%.2f%%" % (errorCount * 100))
